#include "TreeBPlus.h"
#include "../../Commons/Configuration.h"
using namespace Tree;

TreeBPlus::TreeBPlus() {

	string treePath("/home/alterego87/workspace/treeFile.dat");
	this->treeFile = new BlockFileManager(nodeMaxSize, treePath);

	//check if the tree was created before
	if (treeFile->getNumberOfBlocks() == 0) {
		this->rootNode = new NodeLeaf();
		rootNode->setLevel(0);
		((NodeLeaf*) rootNode)->setNextLeaf(0);

		//write the root and set the block ID as NodeID
		int nodeID = treeFile->writeNewBlock(rootNode->buildBuffer());
		rootNode->setEntityID(nodeID);
	} else {
		this->rootNode = new NodeInternal();
		rootNode->fillEntity(treeFile->readBlock(0), nodeMaxSize);
		rootNode->setEntityID(0);
	}

}

void TreeBPlus::addInTree(Record* recordToAdd) {

	RecordNode* recordNodeToAdd = new RecordNode();
	recordNodeToAdd->setKey(recordToAdd->getKey());
	for (unsigned int i = 0; i < recordToAdd->getData()->size(); i++) {
		recordNodeToAdd->getOffsets()->push_back(recordToAdd->getData()->at(i));
	}

	bool rootWasModified = insertInNode(rootNode, recordNodeToAdd);

	if (rootWasModified) {

		if (rootNode->hasOverflow(nodeMaxSize))
			resolveRootOverflow();

		saveNode(rootNode);
	}

	delete recordToAdd;
}

/* Inserts recursively the value in a leaf. Returns true if the node passed by parameter was modified.
 */
bool TreeBPlus::insertInNode(Node* node, RecordNode* recordNodeToAdd) {

	bool toReturn = false;

	if (node->getLevel() == 0) {
		((NodeLeaf*) node)->addInNode(recordNodeToAdd);
		toReturn = true;
	} else {
		int nextNodeNumber = ((NodeInternal*) node)->getChildNodeNumber(recordNodeToAdd->getKey());
		Node* nextNode = createNode(nextNodeNumber);

		bool wasModified = insertInNode(nextNode, recordNodeToAdd);

		if (wasModified) {

			if (nextNode->hasOverflow(nodeMaxSize))
				resolveOverflow(nextNode, (NodeInternal*) node);

			saveNode(nextNode);
			saveNode(node);
			toReturn = true;
		}

		delete nextNode;
	}

	return toReturn;
}

/* Creates a new leaf and tries to set the 'overflowed node' capacity at 50%.
 * Adds the new node to the parent.
 */
void TreeBPlus::resolveOverflow(Node* nodeOverflowed, NodeInternal* parent) {

	Node* newBrother;

	//creates the brother
	if (nodeOverflowed->getLevel() == 0) {
		newBrother = new NodeLeaf();
		((NodeLeaf*) newBrother)->setNextLeaf(nodeOverflowed->getEntityID()); //custom setting 'cause it's a leaf
	} else
		newBrother = new NodeInternal;

	newBrother->setLevel(nodeOverflowed->getLevel());

	//balance the nodes
	balanceNodes(nodeOverflowed, newBrother);

	if (nodeOverflowed->getLevel() > 0)
		newBrother->getRecordNodes()->at(0)->setLeftReference(-1); //custom setting 'cause it's internal

	//save the newly created node
	newBrother->setEntityID(saveNode(newBrother));

	if (nodeOverflowed->getLevel() == 0)
		((NodeLeaf*) nodeOverflowed)->setNextLeaf(nodeOverflowed->getEntityID()); //custom setting 'cause it's a leaf

	//build record to parent
	RecordNode* newRecord = new RecordNode();
	newRecord->setLeftReference(nodeOverflowed->getEntityID());
	newRecord->setRightReference(newBrother->getEntityID());
	newRecord->setKey(newBrother->getRecordNodes()->at(0)->getKey());

	parent->addInNode(newRecord);
	delete newBrother;

}

/*
 * Creates two nodes and balance the data between them, keeping references to the new nodes
 */
void TreeBPlus::resolveRootOverflow() {

	if (rootNode->getLevel() == 0) // it's a leaf
	{
		//separate the data in two leafs
		NodeLeaf* leftChild = new NodeLeaf();
		NodeLeaf* rightChild = new NodeLeaf();

		leftChild->getRecordNodes()->swap(*rootNode->getRecordNodes());
		balanceNodes(leftChild, rightChild);

		//setting the IDs and saving the data
		unsigned int leftChildID;
		unsigned int rightChildID;
		rightChild->setNextLeaf(0);
		rightChildID = saveNode(rightChild);

		leftChild->setNextLeaf(rightChildID); //custom setting because it's a leaf
		leftChildID = saveNode(leftChild);

		//convert the root into an InternalNode, and set the references of the childs
		RecordNode* recordNodeToRoot = new RecordNode();
		recordNodeToRoot->setLeftReference(leftChildID);
		recordNodeToRoot->setKey(rightChild->getRecordNodes()->at(0)->getKey());
		recordNodeToRoot->setRightReference(rightChildID);

		rootNode = new NodeInternal();
		rootNode->setLevel(1);
		rootNode->setEntityID(0);
		rootNode->getRecordNodes()->push_back(recordNodeToRoot);

	} else {

		//separate the data in two internals
		NodeInternal* leftChild = new NodeInternal();
		NodeInternal* rightChild = new NodeInternal();

		leftChild->getRecordNodes()->swap(*rootNode->getRecordNodes());
		balanceNodes(leftChild, rightChild);
		//setting the IDs and saving the data
		unsigned int leftChildID;
		unsigned int rightChildID;
		rightChild->getRecordNodes()->at(0)->setLeftReference(-1); //custom setting 'cause it's internal
		rightChildID = saveNode(rightChild);
		leftChildID = saveNode(leftChild);

		//set the references of the childs
		RecordNode* recordNodeToRoot = new RecordNode();
		recordNodeToRoot->setLeftReference(leftChildID);
		recordNodeToRoot->setRightReference(rightChildID);
		recordNodeToRoot->setKey(rightChild->getRecordNodes()->at(0)->getKey());

		rootNode->setLevel(rootNode->getLevel() + 1);
		rootNode->setEntityID(0);
		rootNode->getRecordNodes()->push_back(recordNodeToRoot);

	}
}

unsigned int TreeBPlus::saveNode(Node* nodeToSave) {

	int blockNumber = nodeToSave->getEntityID();
	char* buffer = nodeToSave->buildBuffer();

	if (blockNumber >= 0)
		treeFile->updateBlock(buffer, blockNumber);
	else
		blockNumber = treeFile->writeNewBlock(buffer);

	return blockNumber;
}

void TreeBPlus::balanceNodes(Node* nodeOverflowed, Node* newBrother) {

	unsigned int newBrother_maxSize = nodeMaxSize / 2;

	while (nodeOverflowed->getBufferSize() > newBrother_maxSize) {

		RecordNode* lastRecord = nodeOverflowed->getRecordNodes()->at(nodeOverflowed->getRecordNodes()->size() - 1);
		newBrother->addInNode(lastRecord);
		nodeOverflowed->getRecordNodes()->resize(nodeOverflowed->getRecordNodes()->size() - 1);
	}

	//we left the leftNode with more data by design
	nodeOverflowed->getRecordNodes()->swap(*newBrother->getRecordNodes());

}

Node* TreeBPlus::createNode(unsigned int nodeID) {

	Node* toReturn;
	char* buffer = treeFile->readBlock(nodeID);
	int* nodeLevel = (int*) buffer;

	if (*nodeLevel == 0)
		toReturn = new NodeLeaf();
	else
		toReturn = new NodeInternal();

	toReturn->fillEntity(buffer, nodeMaxSize);
	toReturn->setEntityID(nodeID);

	return toReturn;
}

void TreeBPlus::removeInTree(string key) {

	bool rootWasModified = removeInNode(rootNode, key);

	if (rootWasModified) {

		saveNode( rootNode);
	}

}

bool TreeBPlus::removeInNode(Node* node, string key) {

	bool toReturn = false;

	if (node->getLevel() == 0) {
		((NodeLeaf*) node)->removeInNode(key);
		toReturn = true;
	} else {
		int nextNodeNumber = ((NodeInternal*) node)->getChildNodeNumber(key);
		Node* nextNode = createNode(nextNodeNumber);

		bool wasModified = removeInNode(nextNode, key);

		if (wasModified) {

			if (nextNode->hasUnderflow())
				resolveUnderflow(nextNode, (NodeInternal*) node);

			saveNode(nextNode);
			saveNode(node);
			toReturn = true;
		}

		delete nextNode;
	}

	return toReturn;
}

void TreeBPlus::resolveUnderflow(Node* nodeWithUnderflow, NodeInternal* nodeParent) {

}

void TreeBPlus::exportToTxt(string name) {
	fstream txtFile;
	Configuration* config = Configuration::getInstance();
	string fileName = config->getReportsFilesPath() + name + ".  txt";
	txtFile.open(fileName.c_str(), ios::out | ios::app);

	if (txtFile.bad()) {
		txtFile.open(fileName.c_str(), ios::out);
	}

	txtFile << "Arbol B+ - Estado actual" << endl;
	int cant = this->treeFile->getNumberOfBlocks();
	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "Nodos Libres : ";
	vector<int>* freeBlocks = this->treeFile->getVecFreeBlocks();

	for (unsigned int i = 0; i < freeBlocks->size(); i++) {
		txtFile << " (" << freeBlocks->at(i) << ") ";
	}

	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "Nodos : " << endl;
	txtFile << endl;
	txtFile << "Raiz : " << endl;
	txtFile << "Level :" << this->rootNode->getLevel() << endl;
	txtFile << "Cantidad Elementos:" << this->rootNode->getRecordNodes()->size();

	for (int i = 1; i < cant; i++) {

		Node* node = this->createNode(i);
		txtFile << "(" << node->getEntityID() << ") (" << node->getLevel() << ") (" << (config->getBlockSize()
				- node->getBufferSize()) << ") ";

		if (node->getLevel() == 0) {
			vector<RecordNode*> * records = node->getRecordNodes();
			for (unsigned int i = 0; i < records->size(); i++) {
				txtFile << endl << "Clave: " << records->at(i)->getKey() << " " << endl;
				txtFile << "Offsets: ";
				for (unsigned int j = 0; j < records->at(i)->getOffsets()->size(); j++) {
					txtFile << records->at(i)->getOffsets()->at(j) << " | ";
				}
			}
			txtFile << " " << endl << " * " << endl;
			delete node;
		} else {
			vector<RecordNode*> * records = node->getRecordNodes();
			for (unsigned int i = 0; i < records->size(); i++) {
				txtFile << endl << "Clave: " << records->at(i)->getKey() << " " << endl;
				txtFile << "Referencia Izquierda: " << records->at(i)->getLeftReference() << "" << "Referencia Derechar: "
						<< records->at(i)->getRightReference() << endl;
			}
		}

	}

	txtFile << " " << endl;
	txtFile << " " << endl;
	txtFile << "**********" << endl;
	txtFile.close();
}

TreeBPlus::~TreeBPlus() {

	delete treeFile;
}
